package com.cheer.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cheer.dao.*;
import com.cheer.dto.UserItem;
import com.cheer.entity.*;
import com.cheer.exception.ServiceException;
import com.cheer.service.ExPresentAudioService;
import com.cheer.vo.ExPresentAudioStatisticsVO;
import com.cheer.vo.ExPresentAudioVO;
import com.cheer.vo.ExPresentUserAudioByMonthVO;
import com.cheer.vo.ExPresentUserAudioVO;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class ExPresentAudioServiceImpl implements ExPresentAudioService {

    @Autowired
    private ExPresentAudioCountDao exPresentAudioCountDao;

    @Autowired
    private ExPresentAudioDao exPresentAudioDao;

    @Autowired
    private ExPresentAudioGoodDao exPresentAudioGoodDao;

    @Autowired
    private ExPresentAudioVoteDao exPresentAudioVoteDao;

    @Autowired
    private OaLearnClassDao oaLearnClassDao;

    @Autowired
    private OaUserDao oaUserDao;

    @Override
    public ExPresentAudioStatisticsVO getStatistics() {
        ExPresentAudioCountEntity exPresentAudioCountEntity = exPresentAudioCountDao.selectOne();
        if (exPresentAudioCountEntity == null) {
            return null;
        }
        return new ExPresentAudioStatisticsVO().setTotalUserCount(exPresentAudioCountEntity.getTotalCount())
                .setGoodUserCount(exPresentAudioCountEntity.getGoodCount());
    }

    @Override
    public List<ExPresentAudioVO> search(Integer pageNum, Integer pageSize, String name, Integer userId) {
        name = StringUtils.trimToNull(name);
        if (StringUtils.isBlank(name)){
            return new ArrayList<>();
        }
        List<OaUserEntity> users = oaUserDao.selectList(new QueryWrapper<OaUserEntity>().eq("name", name));
        if(CollectionUtils.isEmpty(users)){
            return new ArrayList<>();
        }
        List<Integer> userIds = users.stream().map(OaUserEntity::getId).collect(Collectors.toList());
        List<ExPresentAudioGoodEntity> list = exPresentAudioGoodDao.selectByPageInUserIds(pageNum, pageSize, userIds);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        return getExPresentAudioVO(list, userId);
    }

    @Override
    public List<ExPresentAudioVO> getGoodAudios(Integer pageNum, Integer pageSize, Integer userId) {
        List<ExPresentAudioGoodEntity> list = exPresentAudioGoodDao.selectByPage(pageNum, pageSize);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        return getExPresentAudioVO(list, userId);
    }

    @Override
    public List<ExPresentAudioVO> getLatestAudios(Integer pageNum, Integer pageSize, Integer userId) {
        List<ExPresentAudioGoodEntity> list = exPresentAudioGoodDao.selectByPageOrder(pageNum, pageSize);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        return getExPresentAudioVO(list, userId);
    }

    @Override
    public List<ExPresentAudioVO> getAudiosByType(Integer pageNum, Integer pageSize, Integer type, Integer userId) {
        List<ExPresentAudioGoodEntity> list = exPresentAudioGoodDao.selectByType(pageNum, pageSize, type);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        return getExPresentAudioVO(list, userId);
    }

    @Override
    public ExPresentAudioVO getNextGoodAudio(Integer id, Integer userId) {
        ExPresentAudioGoodEntity next;
        ExPresentAudioGoodEntity good = exPresentAudioGoodDao.selectById(id);
        if (good == null) {
            next = exPresentAudioGoodDao.selectByLimit(0);
        } else {
            exPresentAudioGoodDao.selectByVote(good.getVote());

            next = exPresentAudioGoodDao.selectByVote(good.getVote());
        }
        return getExPresentAudioVO(next, userId);
    }

    @Override
    public ExPresentAudioVO getRandomGoodAudio(Integer userId) {
        ExPresentAudioCountEntity audioCount = exPresentAudioCountDao.selectOne();
        int randomInt = RandomUtil.randomInt(audioCount.getGoodCount());
        ExPresentAudioGoodEntity audio = exPresentAudioGoodDao.selectByRandom(randomInt);
        if (audio == null) {
            return null;
        }
        return getExPresentAudioVO(audio, userId);
    }

    @Override
    public ExPresentAudioVO getNextLatestAudio(Integer number, Integer userId) {
        ExPresentAudioEntity audio = exPresentAudioDao.selectByLimit(number);
        if (audio == null) {
            return null;
        }
        return getExPresentAudioVO(audio, userId);
    }

    @Override
    public ExPresentAudioVO getRandomLatestAudio(Integer userId) {
        ExPresentAudioCountEntity audioCount = exPresentAudioCountDao.selectOne();
        int randomInt = RandomUtil.randomInt(audioCount.getTotalCount());
        ExPresentAudioGoodEntity audio = exPresentAudioDao.selectByRandom(randomInt);
        if (audio == null) {
            return null;
        }
        return getExPresentAudioVO(audio, userId);
    }

    @Override
    public ExPresentAudioVO getNextTypeAudio(Integer number, Integer type, Integer userId) {
        ExPresentAudioEntity audio = exPresentAudioDao.selectByTypeLimit(number, type);
        if (audio == null) {
            return null;
        }
        return getExPresentAudioVO(audio, userId);
    }

    @Override
    public ExPresentAudioVO getRandomTypeAudio(Integer type, Integer userId) {
        ExPresentAudioCountEntity audioCount = exPresentAudioCountDao.selectOne();
        int randomInt = RandomUtil.randomInt(audioCount.getTotalCount());
        ExPresentAudioGoodEntity audio = exPresentAudioDao.selectByTypeRandom(randomInt, type);
        if (audio == null) {
            return null;
        }
        return getExPresentAudioVO(audio, userId);
    }

    @Override
    public List<ExPresentUserAudioByMonthVO> getUserAudio(Integer userId) {
        List<ExPresentAudioEntity> list = exPresentAudioDao.selectByUserId(userId);
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }

        OaUserEntity user = oaUserDao.selectById(userId);
        if (user == null) {
            return null;
        }

        Map<Integer, String> departMap = extractDepartmentMap(Lists.newArrayList(user));
        UserItem userItem = buildFromUser(user, departMap);

        Map<String, List<ExPresentUserAudioVO>> map = new LinkedHashMap<>();

        for (ExPresentAudioEntity audioEntity : list) {
            ExPresentUserAudioVO exPresentUserAudioVO = new ExPresentUserAudioVO()
                    .setId(audioEntity.getId())
                    .setAudioUrl(audioUrl + audioEntity.getUrl())
                    .setCreateTime(DateUtil.format(audioEntity.getCreateTime(),
                            DatePattern.NORM_DATETIME_MINUTE_FORMAT))
                    .setDepartment(userItem.getDepartment())
                    .setGood(audioEntity.getStatus() == 3)
                    .setHeadUrl(userItem.getHeadImgUrl())
                    .setName(userItem.getName())
                    .setVoteCount(audioEntity.getVote());

            String dateKey = DateUtil.format(audioEntity.getCreateTime(), "yyyy.M月");
            if (map.containsKey(dateKey)) {
                map.get(dateKey).add(exPresentUserAudioVO);
            } else {
                map.put(dateKey, Lists.newArrayList(exPresentUserAudioVO));
            }
        }

        return map.entrySet().stream()
                .map(entry -> new ExPresentUserAudioByMonthVO()
                        .setMonth(entry.getKey())
                        .setList(entry.getValue()))
                .collect(Collectors.toList());
    }

    @Override
    public UserItem getUserInfo(Integer userId) {
        OaUserEntity user = oaUserDao.selectById(userId);
        if (user == null) {
            return null;
        }
        return new UserItem().setName(user.getName()).setHeadImgUrl(user.getHeadimgurl());
    }

    @Override
    public ExPresentAudioVO getInfo(Integer id, Integer userId) {
        ExPresentAudioEntity audio = exPresentAudioDao.selectById(id);
        if (audio == null) {
            return null;
        }
        return getExPresentAudioVO(audio, userId);
    }

    @Override
    @Transactional
    public void delete(Integer id, Integer userId) {
        ExPresentAudioEntity audio = exPresentAudioDao.selectById(id);
        if (!audio.getUserId().equals(userId)) {
            throw new ServiceException("只能删除自己的献词");
        }
        if (audio.getStatus() == 3) {
            throw new ServiceException("不能删除已评为精选的献词");
        }
        exPresentAudioDao.deleteById(id);


        if (exPresentAudioDao.selectOneByUserId(userId) == null) {
            exPresentAudioCountDao.decrTotalCountAndUserCount();
        } else {
            exPresentAudioCountDao.decrTotalCount();
        }
    }

    @Override
    @Transactional
    public Integer generate(String serviceId, Integer userId) {
        OaUserEntity user = oaUserDao.selectById(userId);
        if (user == null) {
            throw new ServiceException("用户不存在");
        }

        if (exPresentAudioDao.selectOneByUserId(userId) == null) {
            exPresentAudioCountDao.incrTotalCountAndUserCount();
        } else {
            exPresentAudioCountDao.incrTotalCount();
        }

        ExPresentAudioEntity exPresentAudioEntity = new ExPresentAudioEntity();
        exPresentAudioEntity.setUserId(userId);
        exPresentAudioEntity.setType(getType(user.getType()));
        exPresentAudioEntity.setStatus(1);
        exPresentAudioEntity.setDepartment(Integer.valueOf(user.getDepartment()));
        exPresentAudioEntity.setPath(user.getPath());

        exPresentAudioDao.insert(exPresentAudioEntity);

        return exPresentAudioEntity.getId();
    }

    private Integer getType(String type) {
        if ("团员".equals(type)) {
            return 1;
        } else if ("青年".equals(type)) {
            return 2;
        } else if ("团干部".equals(type)) {
            return 3;
        } else {
            return 4;
        }
    }

    @Override
    @Transactional
    public void vote(Integer id, Boolean vote, Integer userId) {
        List<ExPresentAudioVoteEntity> list = exPresentAudioVoteDao.selectByUserId(userId);
        if (list.size() >= 10) {
            throw new ServiceException("您今天点赞次数已用光");
        }

        ExPresentAudioVoteEntity voteEntity = new ExPresentAudioVoteEntity();
        voteEntity.setUserId(userId).setAudioId(id);
        exPresentAudioVoteDao.insert(voteEntity);

        exPresentAudioDao.incrVote(id);
        exPresentAudioGoodDao.incrVote(id);
    }

    @Override
    public void updateByUserId(Integer id, String department, String path) {
        ExPresentAudioEntity entity = new ExPresentAudioEntity();
        entity.setUserId(id);
        entity.setDepartment(Integer.valueOf(department));
        entity.setPath(path);
        exPresentAudioDao.updateByUserId(entity);

        ExPresentAudioGoodEntity goodEntity = new ExPresentAudioGoodEntity();
        goodEntity.setUserId(id);
        goodEntity.setDepartment(Integer.valueOf(department));
        goodEntity.setPath(path);
        exPresentAudioGoodDao.updateByUserId(goodEntity);
    }

    private ExPresentAudioVO getExPresentAudioVO(ExPresentAudioGoodEntity audio, Integer userId) {
        OaUserEntity user = oaUserDao.selectById(audio.getUserId());
        if (user == null) {
            return null;
        }
        ExPresentAudioVO exPresentAudioVO = new ExPresentAudioVO();
        exPresentAudioVO.setId(audio.getId());
        exPresentAudioVO.setName(user.getName());
        exPresentAudioVO.setHeadUrl(user.getHeadimgurl());
        exPresentAudioVO.setAudioUrl(audioUrl + audio.getUrl());
        exPresentAudioVO.setCreateTime(DateUtil.format(audio.getCreateTime(),
                DatePattern.NORM_DATETIME_MINUTE_FORMAT));
        exPresentAudioVO.setVoteCount(audio.getVote());
        exPresentAudioVO.setVoteStatus(CollectionUtils.isNotEmpty(exPresentAudioVoteDao.selectByUserIdAndAudioId(userId, audio.getId())));
        return exPresentAudioVO;
    }

    @Value("${audio.url}")
    private String audioUrl;

    private List<ExPresentAudioVO> getExPresentAudioVO(List<? extends ExPresentAudioGoodEntity> list, Integer userId) {
        List<Integer> userIds = list.stream().map(ExPresentAudioGoodEntity::getUserId).collect(Collectors.toList());
        List<UserItem> users = listUserItems(userIds);
        Map<Integer, UserItem> userMap = users.stream().collect(Collectors.toMap(UserItem::getId, Function.identity()));


        List<Integer> audioIds = list.stream().map(ExPresentAudioGoodEntity::getId).collect(Collectors.toList());
        List<ExPresentAudioVoteEntity> voteList = exPresentAudioVoteDao.selectByUserIdAndAudioId(userId, audioIds);
        Set<Integer> audioIdSet = voteList.stream().map(ExPresentAudioVoteEntity::getAudioId).collect(Collectors.toSet());

        return list.stream().map(exPresentAudioGoodEntity -> {
            UserItem userItem = userMap.get(exPresentAudioGoodEntity.getUserId());
            if (userItem == null) {
                return null;
            }
            ExPresentAudioVO exPresentAudioVO = new ExPresentAudioVO();
            exPresentAudioVO.setId(exPresentAudioGoodEntity.getId());
            exPresentAudioVO.setName(userItem.getName());
            exPresentAudioVO.setHeadUrl(userItem.getHeadImgUrl());
            exPresentAudioVO.setDepartment(userItem.getDepartment());
            exPresentAudioVO.setAudioUrl(audioUrl + exPresentAudioGoodEntity.getUrl());
            exPresentAudioVO.setCreateTime(DateUtil.format(exPresentAudioGoodEntity.getCreateTime(),
                    DatePattern.NORM_DATETIME_MINUTE_FORMAT));
            exPresentAudioVO.setVoteCount(exPresentAudioGoodEntity.getVote());
            exPresentAudioVO.setVoteStatus(audioIdSet.contains(exPresentAudioGoodEntity.getId()));
            return exPresentAudioVO;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    private List<UserItem> listUserItems(List<Integer> userIds) {
        if (CollectionUtils.isEmpty(userIds)) {
            return new ArrayList<>();
        }

        List<OaUserEntity> users = oaUserDao.selectBatchIds(userIds);
        if (CollectionUtils.isEmpty(users)) {
            return new ArrayList<>();
        }

        Map<Integer, String> departmentMap = extractDepartmentMap(users);

        return users.stream()
                .map(user -> buildFromUser(user, departmentMap))
                .collect(Collectors.toList());
    }

    private Map<Integer, String> extractDepartmentMap(List<OaUserEntity> users) {
        List<Integer> departmentIds = users.stream()
                .map(OaUserEntity::getPath)
                .flatMap(departmentPath -> Arrays.stream(StringUtils.split(departmentPath, ",")))
                .distinct()
                .map(Integer::valueOf)
                .filter(departmentId -> departmentId != 0)
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(departmentIds)) {
            return new HashMap<>();
        }

        return oaLearnClassDao.selectBatchIds(departmentIds).stream()
                .collect(Collectors.toMap(OaLearnClassEntity::getId, OaLearnClassEntity::getName));
    }

    private UserItem buildFromUser(OaUserEntity user, Map<Integer, String> departmentMap) {
        UserItem userItemResponse = new UserItem();
        BeanUtils.copyProperties(user, userItemResponse);
        userItemResponse.setHeadImgUrl(user.getHeadimgurl());

        String department = Arrays.stream(StringUtils.split(user.getPath(), ","))
                .map(Integer::valueOf)
                .filter(departmentId -> departmentId != 0)
                .map(departmentMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.joining("-"));
        userItemResponse.setDepartment(department);

        return userItemResponse;
    }
}